/*
 * Copyright 2001 Alexander Boverman and the original authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "WtExecute.h"
#include "WtContext.h"
#include "WtUtil.h"
#include "WtTable.h"
#include "WtTableUtil.h"
#include "WtBasicCmds.h"
#include "WtWebErrors.h"
#include "WtContextEvents.h"

void WtInitExecState(WtExecutionState *execState, WtContext *w, Tcl_Interp *interp)
{
  memset(execState, 0, sizeof(WtExecutionState));
  execState->context = w;
  execState->interp = interp;
}

void WtDeleteExecState(WtExecutionState *execState)
{
  if (execState->requestedPath) {
    Tcl_DecrRefCount(execState->requestedPath);
    execState->requestedPath = NULL;
  }

  if (execState->targetPath) {
    Tcl_DecrRefCount(execState->targetPath);
    execState->targetPath = NULL;
  }

  if (execState->namesp) {
    Tcl_DecrRefCount(execState->namesp);
    execState->namesp = NULL;
  }

  if (execState->genCode) {
    Tcl_DecrRefCount(execState->genCode);
    execState->genCode = NULL;
  }
}

int WtFindAndExecuteFile(WtContext *w, Tcl_Obj *requestedPath, Tcl_Interp *interp)
{
  int ok = 0;
  WtExecutionState execState;

  WtInitExecState(&execState, w, interp);

  execState.requestedPath = requestedPath;
  Tcl_IncrRefCount(execState.requestedPath);

  execState.targetPath = WtFindFile(&execState);

  if (execState.targetPath) {
    Tcl_IncrRefCount(execState.targetPath);
    ok = WtExecuteFile(&execState);
  }

  WtDeleteExecState(&execState);

  return ok;
}

Tcl_Obj *WtFindFile(WtExecutionState *execState)
{
  Tcl_Obj *path = WtResolvePath(execState->context,
    execState->requestedPath, NULL);
  if (!path) {
    Tcl_AppendResult(execState->interp, "Failed to find file \"",
                     WtToString(execState->requestedPath), "\": ",
                     Tcl_PosixError(execState->interp), ".", NULL);
  }
  return path;
}

Tcl_Obj *WtResolvePath(WtContext *w, Tcl_Obj *requestedPath, Tcl_Obj *dirs)
{
  int i, len;
  char *bytes, *parts[2], *p;
  Tcl_PathType pathType;
  Tcl_DString joined;
  Tcl_Obj *resolved = NULL, *item;

  bytes = Tcl_GetStringFromObj(requestedPath, &len);
  pathType = Tcl_GetPathType(bytes);

  if (pathType == TCL_PATH_ABSOLUTE || pathType == TCL_PATH_VOLUME_RELATIVE) {
    return Tcl_NewStringObj(bytes, len);
  }

  if (!dirs) {
    dirs = w->web->loader.includeSearchPath;
  }

  /* XXX: Should have an interp */

  if (Tcl_ListObjLength(NULL, dirs, &len) != TCL_OK) {
    return NULL;
  }

  for (i = 0; i < len; ++i) {
    if (Tcl_ListObjIndex(NULL, dirs, i, &item) != TCL_OK) {
      break;
    }
    parts[0] = Tcl_GetStringFromObj(item, NULL);
    parts[1] = bytes;

    Tcl_DStringInit(&joined);
    p = Tcl_JoinPath(2, parts, &joined);

    if (p && Tcl_Access(p, 00) == 0) {
      resolved = Tcl_NewStringObj(p, -1);
    }

    Tcl_DStringFree(&joined);

    if (resolved) {
      return resolved;
    }
  }

  return NULL;
}

int WtExecuteFile(WtExecutionState *execState)
{
  int ok = 0, n;
  Tcl_Obj *text;

  WtLog(HERE, APLOG_DEBUG | APLOG_NOERRNO, execState->context,
    "Wtcl: WtExecuteFile (targetPath = \"%s\").",
    Tcl_GetString(execState->targetPath));

  text = WtReadFile(execState->targetPath, &n, execState->interp);

  if (text) {
    ok = WtExecuteString(execState, text, n);
  }

  return ok;
}

Tcl_Obj *WtReadFile(Tcl_Obj *path, int *bytes, Tcl_Interp *interp)
{
  int ok = 0, n = 0;
  Tcl_Channel chan;
  Tcl_Obj *text = NULL;

  chan = Tcl_OpenFileChannel(interp, WtToString(path), "r", 0666);

  if (chan) {
    if (Tcl_SetChannelOption(interp, chan, "-translation",
        "binary") == TCL_OK) {
      text = Tcl_NewObj();
      Tcl_IncrRefCount(text);
      n = Tcl_ReadChars(chan, text, -1, 0);
      if (n == -1) {
        Tcl_AppendResult(interp, "Failed to read file \"",
          path, "\": ", Tcl_PosixError(interp), ".", NULL);
      } else {
        ok = 1;
      }
    }
    if (Tcl_Close(interp, chan) != TCL_OK) {
      ok = 0;
    }
  }

  if (ok) {
    if (bytes) {
      *bytes = n;
    }
  } else {
    if (text) {
      Tcl_DecrRefCount(text);
      text = NULL;
    }
    if (bytes) {
      *bytes = -1;
    }
  }

  return text;
}

int WtExecuteString(WtExecutionState *execState, Tcl_Obj *string, int len)
{
  int status = WT_STATUS_OK, rc, errorSetId = -1, errorCount;
  Tcl_Obj *frame, *returnValue = NULL, *errMsg;
  WtContext *w = execState->context;
  WtWebContext *web = execState->context->web;
  char str[128];

  WtLog(HERE, APLOG_DEBUG | APLOG_NOERRNO, w,
    "Wtcl: WtExecuteString (len = %d).", len);

  if (!WtStartErrors(w, execState->interp, &errorSetId)) {
    status = WT_STATUS_FATAL;
  } else {
    /* Add script to stack */

    frame = WtNewTableObj();
    Tcl_IncrRefCount(frame);
    WtTableSetStrToStr(frame, "state", "start");
    WtTableSetStrToObj(frame, "requestedPath", execState->requestedPath ?
      execState->requestedPath : WtNewString(NULL));
    WtTableSetStrToObj(frame, "targetPath", execState->targetPath ?
      execState->targetPath : WtNewString(NULL));
    WtTableSetStrToObj(frame, "namespace",
      execState->namesp ? execState->namesp : WtNewString(NULL));
    WtTableSetStrToStr(frame, "parsed", "");
    WtTableSetStrToBool(frame, "errorState", 0);

    web->loader.pageIndex++;

    if (!WtSetCurrentFrame(w, frame, execState->interp)) {
      status = WT_STATUS_FATAL;
      WtAddInterpError(execState->interp, w);
    }
    Tcl_DecrRefCount(frame);
    frame = NULL;
  }

  if (status == WT_STATUS_OK) {
    /* Start hook */
    if (!WtEvalEventHandlers(&w->web->ctxEventHandlers, "startInclude",
        execState->interp, w)) {
      status = WT_STATUS_ERROR;
      WtAddInterpError(execState->interp, w);

      if (!WtSetFrameError(w, execState->interp)) {
        status = WT_STATUS_FATAL;
        WtAddInterpError(execState->interp, w);
      }
    }
  }

  /* Parse script */

  if (status == WT_STATUS_OK) {
    if (!(frame = WtGetCurrentFrame(w, execState->interp))) {
      status = WT_STATUS_FATAL;
      WtAddInterpError(execState->interp, w);
    } else {
      WtTableSetStrToStr(frame, "state", "parse");
      if (!WtParseString(execState, string, len)) {
        status = WT_STATUS_ERROR;
        WtAddInterpError(execState->interp, w);
        WtTableSetStrToBool(frame, "errorState", 1);
      }
      if (execState->genCode) {
        WtTableSetStrToObj(frame, "parsed", execState->genCode);
      }
      if (!WtSetCurrentFrame(w, frame, execState->interp)) {
        status = WT_STATUS_FATAL;
        WtAddInterpError(execState->interp, w);
      }
      Tcl_DecrRefCount(frame);
      frame = NULL;
    }
  }

  /* Run the script */

  if (status == WT_STATUS_OK &&
      !WtSetFrameState(w, execState->interp, "run")) {
    status = WT_STATUS_FATAL;
    WtAddInterpError(execState->interp, w);
  }

  if (status == WT_STATUS_OK) {
    WtLog(HERE, APLOG_DEBUG | APLOG_NOERRNO, execState->context,
      "Wtcl: WtExecuteString: interp = 0x%x, genCode = \"%s\".",
      execState->interp, Tcl_GetString(execState->genCode));

    rc = Tcl_EvalObj(execState->interp, execState->genCode);

    if (rc == TCL_OK) {
      returnValue = Tcl_GetObjResult(execState->interp);
      if (returnValue) {
        Tcl_IncrRefCount(returnValue);
      }
    } else {
      status = WT_STATUS_ERROR;

      if (execState->targetPath &&
          Tcl_GetCharLength(execState->targetPath)) {
        errMsg = Tcl_NewStringObj("\n    (\"", 7);
        Tcl_IncrRefCount(errMsg);
        Tcl_AppendObjToObj(errMsg, execState->targetPath);
        sprintf(str, "\" include body line %d)",
          execState->interp->errorLine);
        Tcl_AppendToObj(errMsg, str, -1);
        Tcl_AddObjErrorInfo(execState->interp,
          WtToString(errMsg), -1);
        Tcl_DecrRefCount(errMsg);
      } else {
        sprintf(str, "\n    (include body line %d)",
          execState->interp->errorLine);
        Tcl_AddObjErrorInfo(execState->interp, str, -1);
      }

      WtAddEvalError(execState->interp, w);

      if (!WtSetFrameError(w, execState->interp)) {
        status = WT_STATUS_FATAL;
        WtAddInterpError(execState->interp, w);
      }
    }

    WtLog(HERE, APLOG_DEBUG | APLOG_NOERRNO, execState->context,
      "Wtcl: WtExecuteString: Tcl_EvalObj returned %d.", rc);
  }

  /* End hook */

  if (status != WT_STATUS_FATAL) {
    if (!WtSetFrameState(w, execState->interp, "end")) {
      status = WT_STATUS_FATAL;
      WtAddInterpError(execState->interp, w);
    } else {
      if (!WtEvalEventHandlers(&w->web->ctxEventHandlers, "endInclude",
          execState->interp, w)) {
        status = WT_STATUS_ERROR;
        if (!WtAddInterpError(execState->interp, w)) {
          status = WT_STATUS_FATAL;
        }
        if (!WtSetFrameError(w, execState->interp)) {
          status = WT_STATUS_FATAL;
          WtAddInterpError(execState->interp, w);
        }
      }
    }
  }

  if (!WtSetFrameState(w, execState->interp, "done")) {
    status = WT_STATUS_FATAL;
    WtAddInterpError(execState->interp, w);
  }

  /* Pop script stack */

  web->loader.pageIndex--;

  /* Set return */

  if (errorSetId != -1) {
    errorCount = WtStopErrors(w, execState->interp, errorSetId, 1);
    if (errorCount == -1) {
      status = WT_STATUS_FATAL;
    } else if (errorCount == 0 && returnValue) {
      Tcl_SetObjResult(execState->interp, returnValue);
    }
  }

  if (returnValue) {
    Tcl_DecrRefCount(returnValue);
  }

  WtLog(HERE, APLOG_DEBUG | APLOG_NOERRNO, execState->context,
    "Wtcl: WtExecuteString: Returning %d.", status == WT_STATUS_OK);

  return status == WT_STATUS_OK;
}

/* TODO: Add support for UTF. */

int WtParseString(WtExecutionState *execState, Tcl_Obj *string, int len)
{
  int ok = 1, inExpr = 0, inComment = 0;
  char *startStr, *endStr;
  int startLen, endLen;
  char *buf = Tcl_GetString(string);
  char *p = buf, *q, *end = buf + len;
  char exprChar = '=';
  char commentChar = '#';
  Tcl_Obj *namesp;

  WtLog(HERE, APLOG_DEBUG | APLOG_NOERRNO, execState->context,
    "Wtcl: WtParseString (len = %d).", len);

  if (!execState->genCode) {
    execState->genCode = Tcl_NewStringObj(NULL, 0);
    Tcl_IncrRefCount(execState->genCode);
  }

  if (execState->context->web->loader.addHeaderTrailer) {
    if (Tcl_GetCharLength(execState->context->web->loader.includeHeader)) {
      Tcl_AppendObjToObj(execState->genCode,
        execState->context->web->loader.includeHeader);
    }
  }

  namesp = execState->namesp ? execState->namesp :
    execState->context->web->loader.includeNamespace;

  if (namesp && Tcl_GetCharLength(namesp)) {
    Tcl_AppendToObj(execState->genCode, "namespace eval ", -1);
    Tcl_AppendObjToObj(execState->genCode, namesp);
    Tcl_AppendToObj(execState->genCode, " {", 2);
  }

  startStr = WtToString(execState->context->web->loader.scriptStartString);
  startLen = strlen(startStr);

  endStr = WtToString(execState->context->web->loader.scriptEndString);
  endLen = strlen(endStr);

  do {
    q = strstr(p, startStr);

    if (q) {
      if (!WtParserAddContent(execState, p, q - p)) {
        ok = 0;
        break;
      }

      if (q < end - startLen && q[startLen] == exprChar) {
        inExpr = 1;
        p = q + startLen + 1;
      } else if (q < end - startLen && q[startLen] == commentChar) {
        inComment = 1;
        p = q + startLen + 1;
      } else {
        inExpr = 0;
        inComment = 0;
        p = q + startLen;
      }

      q = strstr(p, endStr);

      if (q) {
        if (inExpr) {
          if (!WtParserAddExpr(execState, p, q - p)) {
            ok = 0;
            break;
          }
        } else if (inComment) {
          ;
        } else {
          WtParserAddCode(execState, p, q - p);
        }
        p = q + endLen;
      } else {
        if (execState->targetPath) {
          Tcl_AppendResult(execState->interp,
            "Missing script end tag at end of file \"",
            execState->targetPath, "\".", NULL);
        } else {
          Tcl_AppendResult(execState->interp,
            "Missing script end tag.", NULL);
        }
        ok = 0;
        break;
      }
    } else {
      if (!WtParserAddContent(execState, p, end - p)) {
        ok = 0;
      }
      break;
    }
  } while (p < end && ok);

  if (ok) {
    if (namesp && Tcl_GetCharLength(namesp)) {
      Tcl_AppendToObj(execState->genCode, "\n}", 2);
    }

    if (execState->context->web->loader.addHeaderTrailer) {
      if (Tcl_GetCharLength(execState->context->web->loader.includeTrailer)) {
        Tcl_AppendObjToObj(execState->genCode,
          execState->context->web->loader.includeTrailer);
      }
    }
  }

  return ok;
}

void WtParserAddCode(WtExecutionState *execState, char *buf, int len)
{
  Tcl_AppendToObj(execState->genCode, buf, len);
  Tcl_AppendToObj(execState->genCode, "\n", 1);
}

int WtParserAddExpr(WtExecutionState *execState, char *buf, int len)
{
  if (!len) {
    if (execState->targetPath) {
      Tcl_AppendResult(execState->interp, "Empty expression tag in file \"",
        execState->targetPath, "\".", NULL);
    } else {
      Tcl_AppendResult(execState->interp, "Empty expression tag.", NULL);
    }
    return 0;
  }

  if (Tcl_GetCharLength(execState->context->web->loader.exprHandler)) {
    Tcl_AppendObjToObj(execState->genCode,
      execState->context->web->loader.exprHandler);
    Tcl_AppendToObj(execState->genCode, " ", 1);
    Tcl_AppendToObj(execState->genCode, buf, len);
    Tcl_AppendToObj(execState->genCode, "\n", 1);
  } else {
    Tcl_AppendToObj(execState->genCode, "puts -nonewline ", -1);
    Tcl_AppendToObj(execState->genCode, buf, len);
    Tcl_AppendToObj(execState->genCode, "\n", 1);
  }
  return 1;
}

int WtParserAddContent(WtExecutionState *execState, char *buf, int len)
{
  char *stringLiteral;
  int flags, literalLen;

  if (!len) {
    return 1;
  }

  literalLen = Tcl_ScanCountedElement(buf, len, &flags);
  stringLiteral = Tcl_Alloc(literalLen);
  ap_assert(stringLiteral);

  literalLen = Tcl_ConvertCountedElement(buf, len, stringLiteral, flags);

  if (Tcl_GetCharLength(execState->context->web->loader.textHandler)) {
    Tcl_AppendObjToObj(execState->genCode,
      execState->context->web->loader.textHandler);
    Tcl_AppendToObj(execState->genCode, " ", 1);
    Tcl_AppendToObj(execState->genCode, buf, len);
    Tcl_AppendToObj(execState->genCode, "\n", 1);
  } else {
    Tcl_AppendToObj(execState->genCode, "puts -nonewline [subst ", -1);
    Tcl_AppendToObj(execState->genCode, stringLiteral, literalLen);
    Tcl_AppendToObj(execState->genCode, "]\n", 2);
  }

  Tcl_Free(stringLiteral);
  return 1;
}

int WtExecuteRequest(WtContext *w, const char *requestedPath)
{
  int ok = 0;
  WtExecutionState execState;
  Tcl_Obj *result;

  WtLog(HERE, APLOG_DEBUG | APLOG_NOERRNO, w,
    "Wtcl: WtExecuteRequest (requestedPath = \"%s\").",
    requestedPath);

  WtInitExecState(&execState, w, w->web->interp);

  execState.requestedPath = Tcl_NewStringObj(requestedPath, -1);
  Tcl_IncrRefCount(execState.requestedPath);

  execState.targetPath = Tcl_NewStringObj(requestedPath, -1);
  Tcl_IncrRefCount(execState.targetPath);

  execState.genCode = NULL;

  if (Tcl_GetCharLength(w->web->taskNamespace)) {
    execState.namesp = w->web->taskNamespace;
    Tcl_IncrRefCount(execState.namesp);
  }

  /* Execute the script */

  w->web->pageIsStarted = 1;
  ok = WtExecuteFile(&execState);
  w->web->pageIsStarted = 0;
  if (!ok) {
    result = Tcl_GetObjResult(execState.interp);
    WtLog(HERE, APLOG_ERR | APLOG_NOERRNO, w, "%s",
      (result ? WtSafeStr(WtToString(result)) : ""));
  }

  /* Cleanup */

  WtDeleteExecState(&execState);

  WtLog(HERE, APLOG_DEBUG | APLOG_NOERRNO, execState.context,
    "Wtcl: WtExecuteRequest: Returning %d.", ok);

  return ok;
}

Tcl_Obj *WtGetCurrentFrame(WtContext *w, Tcl_Interp *interp)
{
  Tcl_Obj *frame = NULL;
  int created;

  if (Tcl_ListObjIndex(interp, w->web->loader.pageStack,
      w->web->loader.pageIndex, &frame) == TCL_OK) {
    created = WtCopyOnWrite(&frame);
    if (!WtConvertToTable(frame, interp)) {
      if (created) {
        Tcl_DecrRefCount(frame);
      }
      frame = NULL;
    }
    if (frame) {
      Tcl_IncrRefCount(frame);
    }
  }

  return frame;
}

int WtSetCurrentFrame(WtContext *w, Tcl_Obj *frame, Tcl_Interp *interp)
{
  int ok = 0, len, rc;

  Tcl_IncrRefCount(frame);
  WtCopyOnWrite(&w->web->loader.pageStack);
  if (Tcl_ListObjLength(interp, w->web->loader.pageStack, &len) == TCL_OK) {
    rc = Tcl_ListObjReplace(interp, w->web->loader.pageStack,
      w->web->loader.pageIndex, len - w->web->loader.pageIndex,
      1, &frame);
    ok = rc == TCL_OK;
  }
  Tcl_DecrRefCount(frame);

  return ok;
}

int WtSetFrameState(WtContext *w, Tcl_Interp *interp, const char *state)
{
  int ok = 0;
  Tcl_Obj *frame;

  if (frame = WtGetCurrentFrame(w, interp)) {
    WtTableSetStrToStr(frame, "state", state);
    if (WtSetCurrentFrame(w, frame, interp)) {
      ok = 1;
    }
    Tcl_DecrRefCount(frame);
  }

  return ok;
}

int WtSetFrameError(WtContext *w, Tcl_Interp *interp)
{
  int ok = 0;
  Tcl_Obj *frame;

  if (frame = WtGetCurrentFrame(w, interp)) {
    WtTableSetStrToBool(frame, "errorState", 1);
    if (WtSetCurrentFrame(w, frame, interp)) {
      ok = 1;
    }
    Tcl_DecrRefCount(frame);
  }

  return ok;
}

/* Initialize commands */

void WtInitPageCommands(Tcl_Interp *interp)
{
  WtContext *w = WtGetAssocContext(interp);

  Tcl_CreateObjCommand(interp, "::wt::page::include",
    WtIncludeCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::page::includeString",
    WtIncludeStringCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::page::pageIndex",
    WtPageIndexCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::page::pageStack",
    WtStackCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::page::loaderInfo",
    WtLoaderInfoCmd, NULL, NULL);
}

/* include command */

int WtIncludeCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w = WtGetAssocContext(interp);

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      Tcl_GetString(objv[0]), " path", NULL);
  } else if (WtFindAndExecuteFile(w, objv[1], interp)) {
    ret = TCL_OK;
  }

  return ret;
}

/* includeString command */

int WtIncludeStringCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w = WtGetAssocContext(interp);
  WtExecutionState execState;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      Tcl_GetString(objv[0]), " string", NULL);
  } else {
    WtInitExecState(&execState, w, interp);
    if (WtExecuteString(&execState, objv[1], Tcl_GetCharLength(objv[1]))) {
      ret = TCL_OK;
    }
    WtDeleteExecState(&execState);
  }

  return ret;
}

/* pageIndex command */

int WtPageIndexCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w;

  if (objc != 1) {
    Tcl_AppendResult(interp, wtBadUsagePrefix, WtToString(objv[0]), NULL);
  } else {
    w = WtGetAssocContext(interp);
    Tcl_SetObjResult(interp, Tcl_NewIntObj(w->web->loader.pageIndex));
    ret = TCL_OK;
  }

  return ret;
}

/* stack command */

int WtStackCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w = WtGetAssocContext(interp);

  if (objc != 1) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), NULL);
  } else {
    Tcl_SetObjResult(interp, w->web->loader.pageStack);
    ret = TCL_OK;
  }

  return ret;
}

/* loaderInfo command */

int WtLoaderInfoCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w;
  WtPageLoader *loader;
  Tcl_Obj *tbl;

  if (objc != 1) {
    Tcl_AppendResult(interp, wtBadUsagePrefix, WtToString(objv[0]), NULL);
  } else {
    w = WtGetAssocContext(interp);
    loader = &w->web->loader;
    tbl = WtNewTableObj();
    WtTableSetStrToObj(tbl, "includeSearchPath", loader->includeSearchPath);
    WtTableSetStrToObj(tbl, "includeNamespace", loader->includeNamespace);
    WtTableSetStrToBool(tbl, "addHeaderTrailer", loader->addHeaderTrailer);
    WtTableSetStrToObj(tbl, "includeHeader", loader->includeHeader);
    WtTableSetStrToObj(tbl, "includeTrailer", loader->includeTrailer);
    WtTableSetStrToObj(tbl, "scriptStartString", loader->scriptStartString);
    WtTableSetStrToObj(tbl, "scriptEndString", loader->scriptEndString);
    WtTableSetStrToBool(tbl, "interpolateText", loader->interpolateText);
    WtTableSetStrToObj(tbl, "textHandler", loader->textHandler);
    WtTableSetStrToObj(tbl, "exprHandler", loader->exprHandler);
    WtTableSetStrToInt(tbl, "pageIndex", loader->pageIndex);
    Tcl_SetObjResult(interp, tbl);
    ret = TCL_OK;
  }

  return ret;
}
